//
// GPSMapEdit
// (c) Konstantin Galichsky (kg@geopainting.com), 2002-2007
//
// OziExplorer PLT (tracks) parser.
//

# include "StdAfx.h"
# include "Map.h"
# include "Types.h"
# include "Datum.h"
# include "Load.h"
# include "Log.h"
# include "Status.h"
# include "ParseHelpers.h"
# include "OziHelpers.h"
# include "fast_floor.h"
# include "Globals.h"

class COziPltLoader : public CMapLoader {
  public:
	// Override.
	virtual bool IsValid (const char * _pData, size_t _cFileSize) const;
	virtual LoadStatus_t Load (const char * _pData, size_t _cFileSize, const char * _strFileName, map_t * _pMap);

  private:
	void ParseTrackpoint (const char * _strData, const char * _strEnd);

	map_t * m_pMap;
	const char * m_pStart, * m_pEnd;

	// The counter of current line.
	size_t m_cFileLine;
	//
	size_t m_cOrdinal;

	size_t m_cTrackWidth;
	COLORREF m_colTrackColor;

	points_t m_points;
	std::vector<trackpoint_info_t> m_points_extra;
	size_t m_cTrackLine;
	size_t m_cTrackBeginPos;
	string_t m_strTrackLabel;
	void SaveTrack (const char * _strEnd);

	const CTransformationBase * m_pTransformation;

	file_refs_t m_file_refs;
};

DECLARE_WPTRK_LOADER (COziPltLoader, "ozi-plt", "plt", "OziExplorer track (*.plt)", ftiTracks);

bool COziPltLoader::IsValid (const char * _pData, size_t _cFileSize) const {
	if (! CheckAnsiFile (_pData, _cFileSize, true))
		return false;

	return
		HasPrefix (_pData, _pData + _cFileSize, "OziExplorer Track Point File", 28) ||
		HasPrefix (_pData, _pData + _cFileSize, "OziExplorer CE Track Point File", 31)
	;
}

void COziPltLoader::SaveTrack (const char * _strEnd) {
	if (m_points.empty ())
		return;

	//
	// Add the track to the map.
	//
	rgn_t & rgn = m_pMap->CreateRgn (rkPolyline);
	rgn.SetTrackType ();

	rgn.pFileRef  = & m_file_refs.back ();
	rgn.cBeginPos = m_cTrackBeginPos;
	rgn.cEndPos   = _strEnd - m_pStart;
	rgn.cFileLine = m_cTrackLine;
//	rgn.cOrdinal  = m_cOrdinal ++;

	rgn.strLabel  = m_strTrackLabel;

	rgn.elements.push_back (rgn_element_t ());
	rgn_element_t & element = rgn.elements.back ();
	element.cFileOffset = m_cTrackBeginPos;

	element.cLevel = 0;
	element.points.swap (m_points);

	track_info_t * const pTrackExtra = rgn.pExtra->wtr.CreateTrack ();
	const size_t cPoints = m_points_extra.size ();
	trackpoints_t & TrackPoints = element.pExtra->TrackPoints;
	TrackPoints.resize (cPoints);
	for (size_t c = 0; c < cPoints; ++ c)
		TrackPoints [c] = m_points_extra [c];
	m_points_extra.clear ();

	pTrackExtra->wFlags |= track_info_t::defLineColor;
	pTrackExtra->colLine = m_colTrackColor;

	pTrackExtra->wFlags |= track_info_t::defLineWidth;
	pTrackExtra->wLineWidth = m_cTrackWidth;
}

void COziPltLoader::ParseTrackpoint (const char * _strData, const char * _strEnd) {
	//
	// Parse fields.
	//

	const char * const strLat      = _strData;
	const char * const strLon      = OziFindNextField (strLat,      _strEnd);
	const char * const strBegin    = OziFindNextField (strLon,      _strEnd);
	const char * const strAltitude = OziFindNextField (strBegin,    _strEnd);
	const char * const strDateTime = OziFindNextField (strAltitude, _strEnd);
//	const char * const strDate     = OziFindNextField (strDateTime, _strEnd);
	if (strBegin == _strEnd || strLat == _strEnd || strLon == _strEnd) {
		ReportWarning (_strData - m_pStart, "Unexpected format of trackpoint.");
		return;
	}

	// New track mark.
	if (ParseDecU (strBegin, _strEnd) != 0) {
		SaveTrack (_strData);

		m_cTrackLine = m_cFileLine;
		m_cTrackBeginPos = _strData - m_pStart;
	}

	// Position.
	point_t point;
	const char * pEnd = NULL;
	if (
		! ParseReal (strLat, _strEnd, & pEnd, point.y) ||
		! ParseReal (strLon, _strEnd, & pEnd, point.x)
	) {
		ReportWarning (_strData - m_pStart, "Unexpected format of trackpoint data.");
		return;
	}

	bool bInvalid = false;
	if (point.x > 180) {
		point.x = 180;
		bInvalid = true;
	}
	if (point.x < -180) {
		point.x = -180;
		bInvalid = true;
	}
	if (point.y > 90) {
		point.y = 90;
		bInvalid = true;
	}
	if (point.y < -90) {
		point.y = -90;
		bInvalid = true;
	}
	if (bInvalid)
		ReportWarning (_strData - m_pStart, "Unexpected position of trackpoint.");

	m_points.push_back (point);

	// Extra data.
	m_points_extra.push_back (trackpoint_info_t ());
	trackpoint_info_t & tpe = m_points_extra.back ();

	// Altitude.
	float fAltitudeInFeet = 0;
	if (ParseReal (strAltitude, _strEnd, & pEnd, fAltitudeInFeet)) {
		if (fAltitudeInFeet > -777) {
			tpe.wFlags |= trackpoint_info_t::defAltitude;
			tpe.fAltitude = fAltitudeInFeet/c_feet_per_m;
		}
	}

	// Date and Time.
	double dfDateTime = 0;
	if (ParseReal (strDateTime, _strEnd, & pEnd, dfDateTime)) {
		if (dfDateTime != 0) {
			tpe.wFlags |= trackpoint_info_t::defTime;
			tpe.dtTime = dfDateTime;
		}
	}

	assert (m_points.size () == m_points_extra.size ());
}

LoadStatus_t COziPltLoader::Load (const char * _pData, size_t _cFileSize, const char * _strFileName, map_t * _pMap) {
	m_pMap = _pMap;
	m_pStart = _pData;
	m_pEnd = _pData + _cFileSize;

	//
	// Parse .plt file.
	//

	m_points.clear ();
	m_points_extra.clear ();

	m_cFileLine = 1;
	m_cOrdinal = 0;

	m_cTrackLine = 1;
	m_cTrackBeginPos = 0;
	m_strTrackLabel = _strFileName;

	m_cTrackWidth = 0;
	m_colTrackColor = -1;

	m_pTransformation = NULL;

	m_file_refs.clear ();
	m_file_refs.push_back (file_ref_t ());
	m_file_refs.back ().strFileName = _strFileName;
	m_file_refs.back ().bAttachment = true;

	SetProgressStatus (0);

	const char * pLine = _pData;
	do {
		if (g_bStop)
			return lsInterrupted;

		const char * const pNextLine = FindNextLine (pLine, m_pEnd);

		if (pLine [0] != '\n' && pLine [0] != '\r') {
			if (m_cFileLine == 2) {
				// Get datum.
				const char * strDatumEnd = pLine;
				while (strDatumEnd < pNextLine) {
					if (* strDatumEnd == ',' || * strDatumEnd == '\r' || * strDatumEnd == '\n')
						break;
					++ strDatumEnd;
				}
				const string_t strDatum (pLine, strDatumEnd);

				if (! FindOziDatumToWGS84 (strDatum.c_str (), m_pTransformation)) {
					m_pTransformation = NULL;
					ReportWarning ("Some unsupported datum is used: '%s'. No datum conversion performed.", strDatum.c_str ());
				}
			} else if (m_cFileLine == 5) {
				const char * const strWidth = OziFindNextField (pLine,    pNextLine);
				const char * const strColor = OziFindNextField (strWidth, pNextLine);
				const char * const strDescr = OziFindNextField (strColor, pNextLine);
				const char * const strSkip  = OziFindNextField (strDescr, pNextLine);

				m_cTrackWidth = ParseDecU (strWidth, pNextLine);
				
				const DWORD dwColor = ParseDecU (strColor, pNextLine);
				m_colTrackColor = dwColor;

				if (strSkip - 1 > strDescr) {
					string_t strLabel (strDescr, GetLineLen (strDescr, strSkip - 1));
					if (! strLabel.empty ()) {
						const size_t cLen = ::strlen (strLabel.c_str ());
						if (cLen < strLabel.size ())
							// NOTE: label contains '\0'; let's trim it.
							m_strTrackLabel.assign (strLabel.c_str (), cLen);
						else
							m_strTrackLabel.swap (strLabel);
					}
				}
			} else if (m_cFileLine == 6) {
				const size_t cPoints = ParseDecU (pLine, pNextLine);
				m_points.reserve (cPoints);
				m_points_extra.reserve (cPoints);
			} else if (m_cFileLine >= 7) {
				ParseTrackpoint (pLine, pNextLine);

				if (m_cFileLine == 7) {
					m_pMap->cHeaderLen = pLine - m_pStart;

					m_cTrackLine = m_cFileLine;
					m_cTrackBeginPos = pLine - m_pStart;
				}
			}
		}

		// Count lines.
		++ m_cFileLine;

		pLine = pNextLine;

		SetProgressStatus (fast_floor ((50.f*(pLine - _pData))/_cFileSize + .5f));
	} while (pLine < _pData + _cFileSize);

	SetProgressStatus (50);

	SaveTrack (m_pEnd);

	// Convert datum.
	if (m_pTransformation) {
		ReportText ("Performing datum conversion to WGS84...");
		m_pMap->TransformPoints (* m_pTransformation);
	}

	m_pMap->file_refs.swap (m_file_refs);

	// Suppress warning about empty Zoom etc.
	m_pMap->bNoHeader = true;

	return lsOK;
}